Skip to content

Integrate sherlog memory review roadmap work - #82

Merged
catoncat merged 13 commits into
mainfrom
agent/sherlog-memory-review-stack
Jun 28, 2026
Merged

Integrate sherlog memory review roadmap work#82
catoncat merged 13 commits into
mainfrom
agent/sherlog-memory-review-stack

Conversation

@catoncat

@catoncat catoncat commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Purpose

This is a draft integration PR for unified review only. It is not a request to merge immediately.

The goal is to make the completed and verified Sherlog memory-review roadmap work visible in one branch while keeping the individual PRs and issue tracker intact for whichever merge strategy the reviewer chooses.

Included roadmap issues

Covered issues from the deep-review roadmap:

No issue is being closed by this PR. The issues should remain open until the chosen merge path lands and the tracker is reconciled.

Included / related PRs

Integration branch

  • Base: main
  • Head: agent/sherlog-memory-review-stack
  • Current head commit: 9657abc44d19103abe47b5925f1e50e0a75bd334

Verification

Ran after merging the verified branches into agent/sherlog-memory-review-stack:

  • npm run check -> passed: TypeScript plus 32 test files / 212 tests.
  • npm run eval:acceptance -> passed: 6/6 acceptance cases, 0 failures.
  • git diff --check origin/agent/sherlog-memory-review-stack...HEAD -> passed.

The individual PRs also had passing GitHub Actions and AI review checks when checked before this integration PR was opened.

Suggested merge path

Option A: unified review / integration path

  • Review this draft PR as the complete roadmap slice.
  • If accepted, merge this integration PR to main.
  • Then close or mark the still-open individual PRs as superseded where appropriate.

Option B: individual review path

Risks / review focus

  • Acceptance gate behavior: review eval/acceptance-gate.ts, eval/acceptance-gate.test.ts, and eval/run-acceptance-eval.ts to ensure the gate is strict enough without being brittle.
  • Ranking behavior: review src/ranking.ts, src/ranking.test.ts, and docs/RANKING_WEIGHTS.md for query-profile weighting and tie-break behavior.
  • CLI compatibility: review src/cli.ts, src/evidence-read.ts, and tests to ensure find JSON evidence reads are additive and do not change the top-level command surface.
  • Source adapter privacy/contracts: review docs/SOURCE_CONTRACTS.md, src/sources/codex.test.ts, src/sources/claude-code.test.ts, and src/sources/pi.test.ts for projected text boundaries and malformed/unsupported record handling.
  • Cost telemetry: review eval/perf-bench.ts for whether sync/find/read/freshness timing and payload measures are useful enough for future regressions.

Trace: request_id: cxs_create_integration_pr_20260626T064214Z_api_append


Summary by cubic

Integrates the Sherlog memory-review roadmap, anchors session-only evidence with read-range --query, and removes the ineffective query-profile scoring branch while keeping phrase/coverage signals.

  • New Features

    • Evidence-level acceptance gate: npm run eval:acceptance runs fixtures across codex, claude-code, and pi, checks sourceId/sessionRef/matchSource/matchSeq, and verifies read-range context for message and session-only hits.
    • Deterministic evidence reads in find --json: each hit includes an evidenceRead action; message hits use read-range, session-only hits use read-range --query (fallback to read-page if no query), with source-aware sessionRef.
    • Ranking: removed broad/exact branching; keep phrase adjacency and full-term coverage signals, plus command-sequence bonus. See docs/RANKING_WEIGHTS.md.
    • Operation-cost telemetry: npm run eval:perf reports find p50/p95, scanned message count, top hit, and read probe p95; plus coverage/freshness costs. Supports --source, --runs, --read-runs.
    • Source adapter contracts: docs/SOURCE_CONTRACTS.md and tests ensure malformed/unsupported/private records are filtered for codex, claude-code, and pi.
  • Migration

    • No breaking CLI flags. find --json adds an evidenceRead field; session-only hits now return read-range --query instead of read-page. Update consumers that assume exact payload shapes.

Written for commit 3d9f782. Summary will update on new commits.

Review in cubic

@catoncat

Copy link
Copy Markdown
Owner Author

真实数据 agent-as-judge 评测结果

在 4566 sessions / 247K messages 的真实库上跑了 20 个真实 query 的 A/B 对照(main vs 本 PR),又派了 6 个 agent 做真实记忆检索任务(每个 agent 自主决定查询词、自主调 sherlog、自主读 evidenceRead),然后对整体交互打分。

1. DB 兼容性:✅ 安全

PR 没改 src/db/schema.ts,index_version = shlog-v7-source-identity 两分支一致,不需要 re-sync。现有 DB 直接可用。

2. query-profile scoring (#71):🔴 真实数据上无可观测效果

A/B 对照:20 个真实 query 中 top-1 改变 0 个,top-3 有变化的 3 个(只在 #2/#3 位),17 个 top-3 完全一致。手调权重(8/18/10、cwd 18/12、hitCount 1.75/1.25)在真实数据上几乎没影响。

agent 交互验证:6 个 agent 全程没有一个感知到 broad vs exact 的区分帮到了自己。所有 agent 报告 score 贴在 0.016 左右、彼此几乎不可区分(Agent 1:"score 全在 0.015~0.016 量级,结果之间区分度很小";Agent 3:"所有结果 score 几乎一样,难以区分")。query-profile 只在 row-level bonus 上分权,不改变 BM25 基线的低区分度。

issue #71 要求"make query profile scoring real or remove the stale abstraction"。现在 scoring 确实 real 了(代码真的 branch on profile.kind),但 agent 交互证实它没有可观测效果。形式上解决了 issue,实质上无效果。

3. evidenceRead (#72):🟢 真实有效,但 session-only hit 有缺陷

A/B 对照 + agent 交互:6/6 agent 都自然 follow 了 evidenceRead.argv 建议的 read-range/read-page,且都说有帮助(Agent 2:"read-range 一次拿到完整 assistant 结论,5 条关键引文全在里面")。

🔴 但 session-only hit 的 evidenceRead 指向 offset=0,对长 session 无用:

  • 实测 86 条 message 的 pi session 作为 session-only hit,evidenceRead 返回 read-page offset=0,读到的是"hi"/"你好我在"——跟查询完全无关
  • Agent 4 独立复现:66 条 message 的 codex session,session-level hit 的 evidenceRead 指向 offset=0,前 40 条是会话开头寒暄,不是触发 match 的内容
  • Agent 4 找到了 2026-04-24 的设计源头 session(019dbe80 seq 6),设计者当时写的是"不要假造 matchSeq;可以给 snippet 和建议 read-page,或再做轻量原文证据 scan 找真实 anchor"——设计时就知道应该找真实 anchor,但实现选了简单的 offset=0

4. acceptance gate (#70):🟡 循环验证,不能检测真实问题

合成 fixture 和 golden 是同一个人写的——writeAcceptanceFixtures 造数据,acceptanceGoldens 造期望,期望里的 sessionUuid 直接抄 fixture 代码。6/6 通过只证明代码内部自洽。

agent 交互暴露的真实问题 acceptance gate 一个都没覆盖:

  • CJK / 中英混合查询 0 命中(4/6 agent 遇到,如"FTS 索引 thinking block tool result"→0 结果、"2606.24775"→0 结果)
  • score 不可区分(6/6 agent 遇到)
  • session-only evidenceRead 缺陷(见上)

5. source contracts (#73) + perf-bench (#74):🟢 稳

format-drift / 隐私测试(pi/codex/claude-code 三套)写得扎实,真在测畸形 JSON、unsupported record、toolCall 泄漏。perf-bench 加 read-range/read-page 延迟探针,无性能回归(PR p95=561ms,main p95=716ms)。

agent 交互打分

# 任务 完成度 效率 evidenceRead 摩擦 总分/12
1 ranking 权重设计 3 3 3 2 11
2 source adapter 隐私边界 3 2 3 1 9
3 arXiv 论文起点 3 1 2 1 7
4 session-only evidenceRead 缺陷 3 1 3 1 8
5 pi 超长首条消息 bug 3 2 3 2 10
6 DB schema 迁移兼容性 3 3 3 2 11

6/6 agent 最终都完成了任务,平均 9.3/12。evidenceRead + read-range/read-page 是这次 PR 唯一带来真实价值的部分。

merge 建议

可以 merge。evidenceRead + source contracts + perf telemetry 值这个 PR。但:

  1. query-profile scoring 无害但也几乎无益,不要当 Make query profile scoring real, or remove the stale abstraction #71 的解决
  2. acceptance gate 的 6/6 通过不能当"已验证"看
  3. session-only evidenceRead 的 offset=0 缺陷建议开 follow-up issue(我会接着开)

trace: cxs_real_data_agent_judge_eval_20260626T084500Z

Session-only hits (matchSeq===null, matchSource=session) previously
returned read-page offset=0, which for long sessions read the session
beginning (often irrelevant greetings) instead of the relevant evidence.
6/6 dogfood agents confirmed this defect on real data.

When the query is available, buildEvidenceReadAction now returns
read-range --query <query> instead of read-page offset=0. This lets
resolveAnchorSeq use searchTopHitInSession to locate the real evidence
anchor inside the session transcript. When the query finds no message
hits (e.g. session-level match from compact/summary with different
wording), resolveAnchorSeq falls back to seq=0 instead of throwing.

Changes:
- evidence-read.ts: add query to EvidenceReadAction, return read-range
  --query for session-only hits when query is provided
- query/read.ts: resolveAnchorSeq falls back to seq=0 when query finds
  no hits, instead of throwing
- cli.ts: pass query to buildEvidenceReadAction
- acceptance-gate.ts: session-only-compact-context fixture expects
  read-range; readContextIfNeeded supports read-range with query for
  session-only hits
- dogfood-eval-core.ts: desiredContextMode returns read-range for
  session-only hits in auto mode
- dogfood-schema.ts: add query field to DogfoodExpectedContext

Fixes #83
@catoncat

Copy link
Copy Markdown
Owner Author

Additional fix: #83 session-only evidenceRead (pushed to this branch)

Commit 0411613 fixes #83, discovered during the agent-as-judge evaluation above.

Problem

Session-only hits (matchSeq===null, matchSource=session) returned read-page offset=0, which for long sessions read irrelevant session beginnings (greetings) instead of relevant evidence. 6/6 dogfood agents confirmed this defect on real data.

Fix

buildEvidenceReadAction now returns read-range --query <query> for session-only hits when the query is available. This lets resolveAnchorSeq use searchTopHitInSession to locate the real evidence anchor inside the session transcript. When the query finds no message hits, resolveAnchorSeq falls back to seq=0 (no regression).

Verified

  • 213/213 tests pass (3 new tests in evidence-read.test.ts)
  • 6/6 acceptance gate fixtures pass (session-only-compact-context updated to expect read-range)
  • Real data: session-only hits now return read-range --query instead of read-page offset=0

Known limitation

When session-level hits come from compact/summary text, query terms may not appear verbatim in raw messages (e.g. compact says "ranking weights" but messages only say "ranking"). FTS returns 0 hits → fall back to seq=0. This is inherent; a deeper fix would search individual terms or fuzzy match — out of scope for #83.

@catoncat
catoncat marked this pull request as ready for review June 27, 2026 07:19

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 21 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/query/read.ts">

<violation number="1" location="src/query/read.ts:101">
P2: Falling back to `return 0` makes `anchorSeq` ambiguous — identical to a genuine match at seq=0. The `>>` marker and JSON payload will incorrectly suggest the query landed at seq=0 when no message matched.</violation>
</file>

<file name="eval/dogfood-schema.ts">

<violation number="1" location="eval/dogfood-schema.ts:176">
P3: `context.query` validation does not trim whitespace. Whitespace-only query strings are accepted as valid.</violation>

<violation number="2" location="eval/dogfood-schema.ts:176">
P2: `context.query` is parsed but not counted as an expected assertion. Entries using only `expected.context.query` are incorrectly rejected.</violation>
</file>

<file name="eval/perf-bench.ts">

<violation number="1" location="eval/perf-bench.ts:258">
P2: p95 calculation is hardcoded to max for all sample sizes. This turns percentile telemetry into worst-case only and can mask real latency distribution changes.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/query/read.ts
// Query found no message-level hit (e.g. session-level match from title or
// compact). Fall back to seq=0 so read-range still returns a usable window
// instead of throwing — the caller can page forward if needed.
return 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Falling back to return 0 makes anchorSeq ambiguous — identical to a genuine match at seq=0. The >> marker and JSON payload will incorrectly suggest the query landed at seq=0 when no message matched.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/query/read.ts, line 101:

<comment>Falling back to `return 0` makes `anchorSeq` ambiguous — identical to a genuine match at seq=0. The `>>` marker and JSON payload will incorrectly suggest the query landed at seq=0 when no message matched.</comment>

<file context>
@@ -95,6 +95,10 @@ function resolveAnchorSeq(
+    // Query found no message-level hit (e.g. session-level match from title or
+    // compact). Fall back to seq=0 so read-range still returns a usable window
+    // instead of throwing — the caller can page forward if needed.
+    return 0;
   }
 
</file context>

Comment thread eval/dogfood-schema.ts
if (limit) context.limit = limit;
const mustContain = readStringArray(value.mustContain);
if (mustContain) context.mustContain = mustContain;
if (typeof value.query === "string" && value.query.length > 0) context.query = value.query;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: context.query is parsed but not counted as an expected assertion. Entries using only expected.context.query are incorrectly rejected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/dogfood-schema.ts, line 176:

<comment>`context.query` is parsed but not counted as an expected assertion. Entries using only `expected.context.query` are incorrectly rejected.</comment>

<file context>
@@ -153,6 +173,7 @@ function parseContext(value: unknown): DogfoodExpectedContext | undefined {
   if (limit) context.limit = limit;
   const mustContain = readStringArray(value.mustContain);
   if (mustContain) context.mustContain = mustContain;
+  if (typeof value.query === "string" && value.query.length > 0) context.query = value.query;
 
   return Object.keys(context).length > 0 ? context : undefined;
</file context>

Comment thread eval/perf-bench.ts
// 小样本下 p95 数学意义薄弱: 直接取 max 作为 worst-case 近似
if (samplesMs.length === 0) return 0;
if (p >= 0.99) return Math.max(...samplesMs);
if (p >= 0.95) return Math.max(...samplesMs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: p95 calculation is hardcoded to max for all sample sizes. This turns percentile telemetry into worst-case only and can mask real latency distribution changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/perf-bench.ts, line 258:

<comment>p95 calculation is hardcoded to max for all sample sizes. This turns percentile telemetry into worst-case only and can mask real latency distribution changes.</comment>

<file context>
@@ -125,9 +253,9 @@ function median(sorted: number[]): number {
+  // 小样本下 p95 数学意义薄弱: 直接取 max 作为 worst-case 近似
   if (samplesMs.length === 0) return 0;
-  if (p >= 0.99) return Math.max(...samplesMs);
+  if (p >= 0.95) return Math.max(...samplesMs);
   const sorted = [...samplesMs].sort((a, b) => a - b);
   const idx = Math.min(sorted.length - 1, Math.floor(p * sorted.length));
</file context>

Comment thread eval/dogfood-schema.ts
if (limit) context.limit = limit;
const mustContain = readStringArray(value.mustContain);
if (mustContain) context.mustContain = mustContain;
if (typeof value.query === "string" && value.query.length > 0) context.query = value.query;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: context.query validation does not trim whitespace. Whitespace-only query strings are accepted as valid.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/dogfood-schema.ts, line 176:

<comment>`context.query` validation does not trim whitespace. Whitespace-only query strings are accepted as valid.</comment>

<file context>
@@ -153,6 +173,7 @@ function parseContext(value: unknown): DogfoodExpectedContext | undefined {
   if (limit) context.limit = limit;
   const mustContain = readStringArray(value.mustContain);
   if (mustContain) context.mustContain = mustContain;
+  if (typeof value.query === "string" && value.query.length > 0) context.query = value.query;
 
   return Object.keys(context).length > 0 ? context : undefined;
</file context>
Suggested change
if (typeof value.query === "string" && value.query.length > 0) context.query = value.query;
const query = readNonEmptyString(value, "query");
if (query) context.query = query;

@catoncat
catoncat merged commit fc95c7a into main Jun 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant